/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <AceButton.h>
#include <ESP8266httpUpdate.h>
#include "FS.h"

using namespace ace_button;
WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
// 巴法云服务器地址默认即可
#define TCP_SERVER_ADDR "bemfa.com"
// 服务器端口，tcp创客云端口8344
#define TCP_SERVER_PORT "8344"

//*****可以修改的地方******//
String aptype = "006";   // 001插座类型，002灯类型，003风扇类型，004传感器，005空调，006开关，009窗帘
String Name = "总电源";  // 设备昵称，可随意修改
String verSion = "3.1";  // 3.1是tcp协议,1.1是mqtt协议,
String room = "";        // 房间。例如客厅、卧室等，默认空
int adminID = 0;         // 默认空即可。企业id,建议企业用户配置，该设备会自动绑定到该企业下，获取id方法见接入文档5.17节
int protoType = 3;       // 3是tcp设备端口8344,1是MQTT设备
const int OUT_Pin = 12;  // 单片机LED引脚值，GPIO0引脚，其他开发板，修改为自己的引脚，例如NodeMCU开发板修改为D4
const int LedBlink = 13; // 指示灯引脚，可自行修改，如果没有指示灯，建议删除指示灯相关代码
const int buttonPin = 0; // 定义按钮引脚
int failCount = 10;      // 定义失败连接次数
int databc = 0;          // 定义失败连接次数

//**********************//
// 检测是否是第一次连接WIFI
bool firstWIfiConfig = false;
String topic = "";

// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
#define KEEPALIVEATIME 2 * 1000

// tcp客户端相关初始化，默认即可
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接
bool preTCPConnected = false;

// 相关函数初始化
// 连接WIFI
void doWiFiTick();

// TCP初始化连接
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer(String p);

void turnLed(bool out);
void diangshiqi();
void doHttpStick(String msg);
int getLastMonthDays(int currentMonth);

int httpCode = 0;
String UID = "";
String TOPIC = "";
#define HOST_NAME "bemfa"
char config_flag = 0;
#define MAGIC_NUMBER 0xAA

/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
  bool out;
  int I;
};
config_type config;
tm timeinfo;
char packetBuffer[255]; // 发送数据包
WiFiUDP Udp;

// 校准时间参数
const char *ntpServer = "ntp2.aliyun.com";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;

// 传感器参数
float u, i, kwh;        // 电压 电流 功率 电能
float U, I, P, Pk, KWh; // 电压 电流 功率 电能
int CF;                 // 记录脉冲数
int Adj = 0;            // 记录脉冲数溢出标志
float bcAdj = 0;        // 保存脉冲数溢出标志
float Ot = 0;           // 记录溢出次数
float ZuoTiaKWh = 0;
// 定时器参数
Ticker timerTick; // 创建 Ticker 对象
bool dsq = false; // Ticker新增标志位
bool genxin = false;

int f5;

void saveConfig();
void initWiFi();
void loadConfig();
void restoreFactory();
void checkFirstConfig();
void delayRestart(float t);
void apConfig();
void yourFunction();
void genxincharts();
bool processFile(String time, char parameterData[]);
void operateOnDataInFile(const char *filename, String targetChar, String action, float &newData);

// 当升级开始时，打印日志
void update_started()
{
  Serial.println("CALLBACK:  HTTP update process started");
}

// 当升级结束时，打印日志
void update_finished()
{
  Serial.println("CALLBACK:  HTTP update process finished");
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  digitalWrite(LedBlink, !digitalRead(LedBlink)); // 切换指示灯状态
  Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes...\n", cur, total);
}

// 当升级失败时，打印日志
void update_error(int err)
{
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin();
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin()
{
  String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=" + TOPIC + ".bin"; // OTA固件链接，请替换为自己的固件链接，如果接收到msg=update，开始执行固件升级

  Serial.println("start update");
  WiFiClient UpdateClient;

  ESPhttpUpdate.onStart(update_started);     // 当升级开始时
  ESPhttpUpdate.onEnd(update_finished);      // 当升级结束时
  ESPhttpUpdate.onProgress(update_progress); // 当升级中
  ESPhttpUpdate.onError(update_error);       // 当升级失败时

  t_httpUpdate_return ret = ESPhttpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    Serial.println("[update] Update ok.");
    break;
  }
}

// 按钮配置接口
AceButton ledButton(buttonPin);
// 按键处理程序
void handleEvent(AceButton *button, uint8_t eventType, uint8_t)
{
  switch (eventType)
  {
  // 当短按时
  case AceButton::kEventReleased:
    Serial.println(F("Button: Pressed"));
    turnLed(!digitalRead(OUT_Pin));
    break;
  // 当长按时
  case AceButton::kEventLongPressed:
    Serial.println(F("Button: Long Pressed"));
    restoreFactory();
    delayRestart(0);
    break;
  }
}
static unsigned long buttonLastMillis = 0; // 时间戳，用于计算防抖

void IRAM_ATTR checkSwitch()
{

  unsigned long newMillis = millis(); // 获取当前时间戳

  if (newMillis - buttonLastMillis > 30)
  { // 检测短按，是否大于30ms
    Serial.println("low press !!!!!!!!");
    turnLed(!digitalRead(OUT_Pin));
  }
  buttonLastMillis = newMillis; // 重新计算防抖动
}

/**
 * 存储配网信息
 */
void saveConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  config.reboot = 0;
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
Ticker delayTimer;

void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}

/**
 * 加载存储的信息，并检查是否恢复出厂信息
 */
uint8_t *p = (uint8_t *)(&config);
void loadConfig()
{
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }
  // 检查是否需要airkiss配网
  if (config.magic != 0xAA)
  {
    restoreFactory();
    config_flag = 1;
  }
  digitalWrite(OUT_Pin, config.out); // 写入默认状态

  Serial.print("stassid:");
  Serial.println(config.stassid);
  Serial.print("stapsw:");
  Serial.println(config.stapsw);
  Serial.print("cuid:");
  Serial.println(config.cuid);
  Serial.print("ctopic:");
  Serial.println(config.ctopic);
  Serial.print("reboot:");
  Serial.println(config.reboot);
  Serial.print("magic:");
  Serial.println(config.magic);
  Serial.print("out:");
  Serial.println(config.out);
  Serial.print("I:");
  Serial.println(config.I);

  // 初始化 SPIFFS
  if (!SPIFFS.begin())
  {
    Serial.println("SPIFFS 初始化失败");
    SPIFFS.format();
    return;
  }
  else
  {
    // Serial.println("****************读取目录操作开始******************");
    // Dir dirPath = SPIFFS.openDir("/");
    // for (size_t i = 0; dirPath.next(); i++)
    // {
    //   Serial.println(dirPath.fileName());
    // }
    // Serial.println("****************读取目录操作完成******************\n");
    //   //从闪存中删除file_name文件
    // if (SPIFFS.remove("/data.txt")){
    //   Serial.println(" remove sucess");
    // } else {
    //   Serial.println(" remove fail");
    // }
    operateOnDataInFile("/data.txt", "Ot", "R", Ot);
    operateOnDataInFile("/data.txt", "ZuoTiaKWh", "R", ZuoTiaKWh);
    operateOnDataInFile("/data.txt", "bcAdj", "R", bcAdj);
    float adj = 0;
    operateOnDataInFile("/data.txt", "bcAdj", "W", adj);
  }
}
/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.magic = 0x00;
  config.reboot = 0;
  config.out = true;
  config.I = 10;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  strcpy(config.cuid, "");
  strcpy(config.ctopic, "");
  saveConfig();
}

/*
  第一次配网检查WIFI,保存WIFI配置信息,并创建主题
*/
void checkFirstConfig()
{
  if (firstWIfiConfig)
  {
    // 设置目标 URL
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient, "http://pro.bemfa.com/vs/web/v1/deviceAddTopic");
    // 创建 JSON 对象
    StaticJsonDocument<200> jsonDoc;
    jsonDoc["uid"] = config.cuid;
    jsonDoc["name"] = Name;
    jsonDoc["topic"] = topic;
    jsonDoc["type"] = protoType;
    jsonDoc["room"] = room;
    jsonDoc["adminID"] = adminID;
    jsonDoc["wifiConfig"] = 1; // 必填字段

    // 将 JSON 对象转换为字符串
    String jsonString;
    serializeJson(jsonDoc, jsonString);
    http_bemfa_HTTPClient.addHeader("Content-Type", "application/json; charset=UTF-8");
    // 发送请求
    int httpCode = http_bemfa_HTTPClient.POST(jsonString);
    if (httpCode == 200)
    {
      Serial.println("POST succeeded with code:");
      Serial.println(httpCode);
      String payload = http_bemfa_HTTPClient.getString();
      Serial.println(payload);

      // json数据解析
      StaticJsonDocument<200> doc;
      DeserializationError error = deserializeJson(doc, payload);
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
      }

      int code = doc["code"];
      if (code == 0)
      {
        int resCode = doc["data"]["code"];
        if (resCode == 40006 || resCode == 0)
        {
          String docUID = doc["uid"];
          Serial.print("create topic:");
          Serial.println(topic);
          if (firstWIfiConfig)
          {
            config.magic = 0xAA;
            saveConfig();
          }
        }
        else
        {
          Serial.println(" config ERROR.........");
        }
      }
      else
      {
        Serial.println(" config ERROR.........");
      }
    }
    else if (httpCode != 200)
    {
      Serial.println("POST failed with code:");
      Serial.println(httpCode);
    }
    else
    {
      Serial.println("Unknown error");
    }
    http_bemfa_HTTPClient.end();
  }
}

void apConfig()
{
  topic = WiFi.macAddress().substring(8); // 取mac地址做主题用
  topic.replace(":", "");                 // 去掉:号
  topic = topic + aptype;                 // 主题添加类型
  if (config_flag)
  {
    WiFi.softAP("bemfa_" + topic);
    Udp.begin(8266);
    Serial.println("Started Ap Config...");
  }
  while (config_flag)
  { // 如果未配网，开启AP配网，并接收配网信息
    int packetSize = Udp.parsePacket();
    if (packetSize)
    {
      Serial.print("Received packet of size ");
      Serial.println(packetSize);
      Serial.print("From ");
      IPAddress remoteIp = Udp.remoteIP();
      Serial.print(remoteIp);
      Serial.print(", port ");
      Serial.println(Udp.remotePort());

      int len = Udp.read(packetBuffer, 255);
      if (len > 0)
      {
        packetBuffer[len] = 0;
      }
      Serial.println("Contents:");
      Serial.println(packetBuffer);
      StaticJsonDocument<200> doc;

      DeserializationError error = deserializeJson(doc, packetBuffer);
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }
      int cmdType = doc["cmdType"].as<int>();
      if (cmdType == 1)
      {
        const char *ssid = doc["ssid"];
        const char *password = doc["password"];
        const char *token = doc["token"];
        const char *ctopic = topic.c_str();
        strcpy(config.stassid, ssid);
        strcpy(config.stapsw, password);
        strcpy(config.cuid, token);
        strcpy(config.ctopic, ctopic);
        // 收到信息，并回复
        String ReplyBuffer = "{\"cmdType\":2,\"productId\":\"" + topic + "\",\"deviceName\":\"" + Name + "\",\"protoVersion\":\"" + verSion + "\"}";
        Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
        Udp.write(ReplyBuffer.c_str());
        Udp.endPacket();
      }
      else if (cmdType == 3)
      {
        config_flag = 0;
        firstWIfiConfig = true;
        WiFi.softAPdisconnect(true);
        WiFi.disconnect();                         // 断开连接
        WiFi.mode(WIFI_STA);                       // STA模式
        WiFi.begin(config.stassid, config.stapsw); // 连接路由器
      }
    }
    digitalWrite(LedBlink, !digitalRead(LedBlink)); // 切换指示灯状态
    delay(100);
  }
  digitalWrite(LedBlink, HIGH); // 切换指示灯状态
}

/**
 * 检查是否需要airkiss配网
 */
void waitKey()
{
  if (config_flag == 1)
  {
    apConfig();
    int num = 0;
    while (WiFi.status() != WL_CONNECTED)
    {                                                 // 检查是否连接成功
      digitalWrite(LedBlink, !digitalRead(LedBlink)); // 切换指示灯状态
      if (num > 120)
      {
        delayRestart(0);
      }
      delay(500);
      num = num + 1;
      Serial.print(".");
    }
    digitalWrite(LedBlink, HIGH); // 切换指示灯状态
    checkFirstConfig();
  }
}
// 函数用于将多个变量相加后判断是否发生变化
bool check_and_convert_sum_change(int var1, int var2, int var3)
{
  static int prev = 0; // 静态变量
  int sum = var1 + var2 + var3;
  bool has = prev != sum;
  prev = sum;
  return has;
}

void sendtoTCPServer()
{
  sendtoTCPServer("cmd=2&uid=" + UID +
                  "&topic=" + TOPIC +
                  "/up&msg=" + (config.out ? "on" : "off") +
                  "#" + U +         // 电压
                  "#" + I +         // 电流
                  "#" + P +         // 功率
                  "#" + KWh +       // 电能
                  "#" + config.I +  // 最大电流
                  "#" + Adj +       // 脉冲溢出标志
                  "#" + bcAdj +     // 脉冲溢出标志
                  "#" + CF +        // 脉冲
                  "#" + Ot +        // 溢出次数
                  "#" + ZuoTiaKWh + // 记录昨天的总脉冲数
                  "#" + f5 +        // 主循环频率
                  "#" + databc +    // 数据报错次数
                  "#\r\n");         // 推送消息
}

/*
 *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{

  if (!client_bemfa_WiFiClient.connected())
  {
    Serial.println("Client is not readly");
    return;
  }
  client_bemfa_WiFiClient.print(p);
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}

/*
 *初始化和服务器建立连接
 */
void startTCPClient()
{
  static int Count = 0;

  if (client_bemfa_WiFiClient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    Serial.println("bemfa.com config ok");
    Serial.printf("%s: %d\r\n", TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT));
    String tcpTemp = "";                                       // 初始化字符串
    tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC + "\r\n"; // 构建订阅指令
    sendtoTCPServer(tcpTemp);                                  // 发送订阅指令
    Serial.println(tcpTemp);                                   // 发送订阅指令
    tcpTemp = "";                                              // 清空
    client_bemfa_WiFiClient.setNoDelay(true);
    failCount = 0;
  }
  else
  {
    Count = Count + 1;
    if (failCount < Count) // 如果失败连接10次，重启系统
    {
      delayRestart(0);
    }
    Serial.print("Failed connected to server:");
    Serial.println(TCP_SERVER_ADDR);
    client_bemfa_WiFiClient.stopAll();
  }
  preTCPStartTick = millis();
}

/*
 *检查数据，发送心跳
 */
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
  {
    preTCPStartTick = millis();
    return;
  }
  if (!client_bemfa_WiFiClient.connected()) // 断开重连
  {
    if (millis() - preTCPStartTick > 3000) // 重新连接
    {
      startTCPClient();
    }
  }
  else
  {
    if (client_bemfa_WiFiClient.available())
    { // 收数据
      char c = client_bemfa_WiFiClient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();

      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE - 2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME) // 保持心跳
    {
      diangshiqi(); // 定时器
      sendtoTCPServer("cmd=0&msg=keep\r\n");
      if (check_and_convert_sum_change(U, I * 10, P * 10))
      {
        sendtoTCPServer();
      }
      f5 = 0;
    }
  }
  if ((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick >= 10))
  {
    Serial.println("数据:" + TcpClient_Buff);
    preTCPStartTick = millis();
    client_bemfa_WiFiClient.flush();
    if (TcpClient_Buff.indexOf("f5") > 0)
    {
      sendtoTCPServer();
    }
    else if (TcpClient_Buff.indexOf("on") > 0)
    {                    // 如果是消息==打开
      config.out = true; // 改变记录的状态
      turnLed(config.out);
      preHeartTick = 0;
    }
    else if (TcpClient_Buff.indexOf("off") > 0)
    {                     // 如果是消息==关闭
      config.out = false; // 改变记录的状态
      turnLed(config.out);
      preHeartTick = 0;
    }
    else if (TcpClient_Buff.indexOf("delete") > 0)
    {
      Dir dirPath = SPIFFS.openDir("/");
      int a = TcpClient_Buff.indexOf("delete:");
      int b = TcpClient_Buff.indexOf("#", a);
      int c = TcpClient_Buff.indexOf("#", b + 1);

      if (a > 0 && b > 0)
      {
        String VaLue = TcpClient_Buff.substring(a + 7, b);
        if (c < 0 && SPIFFS.remove("/" + VaLue + ".txt"))
        {
          Serial.println(" remove sucess");
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=文件已删除\r\n");
        }
        if (c > 0)
        {
          int kye = atoi((TcpClient_Buff.substring(b + 1, c)).c_str());
          File file = SPIFFS.open("/" + VaLue + ".txt", "r");
          if (file)
          {
            const size_t bytesToKeep = file.size() - kye; // 假设要保留前100字节
            char buffer[bytesToKeep];
            size_t bytesRead = file.read((uint8_t *)buffer, bytesToKeep);
            file.close();
            // 后续步骤
            File file = SPIFFS.open("/" + VaLue + ".txt", "w");
            if (file)
            {
              file.write((uint8_t *)buffer, bytesRead);
              file.close();
            }
          }
          Serial.println(" remove sucess");
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=文件已修改\r\n");
        }
      }
      for (size_t i = 0; dirPath.next(); i++)
      {
        File file = SPIFFS.open(dirPath.fileName(), "r");
        sendtoTCPServer("cmd=2&uid=" + UID +
                        "&topic=" + TOPIC +
                        "/up&msg=" + dirPath.fileName() + " :" + file.size() + "字节\r\n"); // 推送消息
        if (file)
        {
          while (file.available())
          {
            String content = "";
            int length = 127;
            content = file.readStringUntil('\n');
            for (size_t i = 0; i < content.length(); i += length)
            {
              sendtoTCPServer("cmd=2&uid=" + UID +
                              "&topic=" + TOPIC +
                              "/up&msg=" + content.substring(i, min(i + length, content.length())) + "\r\n");
            }
            delay(500);
          }
        }
        file.close();
        delay(500);
      }
    }
    else if (TcpClient_Buff.indexOf("charts") > 0) // 把图表数据发送出去
    {
      if (TcpClient_Buff.indexOf("Resetcharts") > 0) // 清空图表数据
      {
        File charts = SPIFFS.open("/charts.txt", "w");
        charts.close();
        Serial.println("重置图表");
        sendtoTCPServer("cmd=2&uid=" + UID +
                        "&topic=" + TOPIC +
                        "/up&msg=重置图表\r\n");
      }
      else
      {
        int a = TcpClient_Buff.indexOf("charts");
        int b = a + 6;
        char ch[] = ".";
        while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
        {
          b++;
        }
        if (a >= 0 && b > a)
        {
          genxin = true;
          genxincharts();
          String time = TcpClient_Buff.substring(a + 6, b);
          Serial.println("查找" + time + "开头");
          char parameter[125]; // 假设最多存储 100 组参数数据
          if (processFile(time, parameter))
          {
            Serial.printf("找到数据%s\n", parameter);
            sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC +
                            "/up&msg=S" + parameter + "\r\n"); // 推送消息
          }
          else
          {
            Serial.println("没有找到数据");
            sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC +
                            "/up&msg=没有找到数据\r\n"); // 推送消息
          }
        }
      }
    }
    else if (TcpClient_Buff.indexOf("update") > 0)
    {
      Serial.println("[update] Update Start......");
      operateOnDataInFile("/data.txt", "bcAdj", "W", bcAdj);
      updateBin();
    }
    else if (TcpClient_Buff.indexOf("canshu") > 0 || TcpClient_Buff.indexOf("i=") > 0)
    {
      if (TcpClient_Buff.indexOf("i=") > 0)
      {
        char ch[] = ".";
        int a = TcpClient_Buff.indexOf("i=") + 2;
        int b = a;
        while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
        {
          b++;
        }
        if (a < b)
        {
          String I = TcpClient_Buff.substring(a, b);
          int i = atoi(I.c_str());
          if (i > 0)
          {
            config.I = atoi(I.c_str());
            saveConfig();
          }
        }
      }
      sendtoTCPServer("cmd=2&uid=" + UID +
                      "&topic=" + TOPIC +
                      "/up&msg=canshu#" + config.I + "\r\n"); // 推送消息
    }
    else if (TcpClient_Buff.indexOf("Ot=") > 0)
    {
      char ch[] = ".";
      int a = TcpClient_Buff.indexOf("Ot=") + 3;
      int b = a;
      while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
      {
        b++;
      }
      String I = TcpClient_Buff.substring(a, b);
      int i = atoi(I.c_str());
      if (i > 0)
      {
        Ot = atoi(I.c_str());
        saveConfig();
      }
      sendtoTCPServer("cmd=2&uid=" + UID +
                      "&topic=" + TOPIC +
                      "/up&msg=Ot=" + Ot + "\r\n"); // 推送消息
    }
    else if (TcpClient_Buff.indexOf("Reset") > 0) // 重置计量数据
    {
      SPIFFS.format();
    }
    TcpClient_Buff = "";
    TcpClient_BuffIndex = 0;
  }
}

/**************************************************************************
                                 WIFI
***************************************************************************/
/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上，若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick()
{
  static int wifi = 0;
  static bool startSTAFlag = false;
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;
  if (!startSTAFlag)
  {
    startSTAFlag = true;
    WiFi.disconnect();                         // 断开连接
    WiFi.mode(WIFI_STA);                       // STA模式
    WiFi.begin(config.stassid, config.stapsw); // 连接路由器
  }
  // 未连接1s重连
  if (WiFi.status() != WL_CONNECTED)
  {
    if (millis() - lastWiFiCheckTick > 500)
    {
      if (wifi++ >= 30)
      {
        wifi = 0;
        startSTAFlag = false;
      }
      Serial.print(".");
      lastWiFiCheckTick = millis();
      digitalWrite(LedBlink, !digitalRead(LedBlink)); // 切换指示灯状态
    }

    if (preTCPConnected)
    {
      if (config.out == false)
      {
        turnLed(true);
      }
    }
    taskStarted = false;
    preTCPConnected = false;
  }
  // 连接成功建立
  else
  {
    if (taskStarted == false)
    {
      Serial.println("");
      Serial.println("wifi config ok");
      Serial.print("IP: ");
      Serial.println(WiFi.localIP());
      digitalWrite(LedBlink, LOW); // 切换指示灯状态
      startTCPClient();
      taskStarted = true;
    }
  }
}

// 控制灯泡
void turnLed(bool out)
{
  digitalWrite(OUT_Pin, out);
  config.out = out;
  saveConfig();
}

// 函数用于对给定数组元素累加并取低八位
uint8_t accumulateAndGetLow8Bits(const uint8_t hex_numbers[], size_t arraySize)
{
  uint16_t sum = 0;

  // 累加数组中的元素
  for (size_t i = 0; i < arraySize - 2; i++)
  {
    sum += hex_numbers[i];
  }
  // 取低八位
  return static_cast<uint8_t>(sum);
}
/*
 *读取传感器数据
 */
void Serialjieshou()
{
  uint8_t comdata[24];
  int bytyt = 0;
  if (Serial.available() > 0 && 0x5A == char(Serial.read()))
  {
    while (bytyt < 23)
    {
      if (Serial.available() > 0)
      {
        comdata[bytyt] = char(Serial.read());
        ledButton.check(); // 按键检查
        bytyt++;
      }
    }
    if (accumulateAndGetLow8Bits(comdata, bytyt) == comdata[21])
    {
      f5++;
      float Uk = (comdata[0] << 16) | (comdata[1] << 8) | (comdata[2]);
      float Ut = (comdata[3] << 16) | (comdata[4] << 8) | (comdata[5]);

      float Ik = (comdata[6] << 16) | (comdata[7] << 8) | (comdata[8]);
      float It = (comdata[9] << 16) | (comdata[10] << 8) | (comdata[11]);

      Pk = (comdata[12] << 16) | (comdata[13] << 8) | (comdata[14]);
      float Pt = (comdata[15] << 16) | (comdata[16] << 8) | (comdata[17]);

      Adj = comdata[18];
      CF = (comdata[19] << 8) | (comdata[20]);

      U = Uk / Ut;
      I = Ik / It;
      P = Pk / Pt;

      if (bcAdj == 0 && (Adj == 241 || Adj == 113)) // 记录开机后的溢出标识
      {
        bcAdj = Adj;
      }

      if ((bcAdj == 241 && Adj == 113) || (bcAdj == 113 && Adj == 241))
      {
        Ot++;
        bcAdj = Adj;
        operateOnDataInFile("/data.txt", "Ot", "W", Ot);
      }

      if (Adj != 241 && Adj != 113)
      {
        I = 0;
        P = 0;
      }
      KWh = (Ot * 65536 + CF) / (1000000000 / Pk * 3600);
      if (I > config.I)
      {
        doHttpStick("电流大于设定值" + String(I) + "A");
        turnLed(false);                      // 关闭
        timerTick.attach(10, turnLed, true); // 十秒后重新打开电源
      }
    }
    else
    {
      databc++;
      size_t i;
      String data = "";
      String p = "";
      for (i = 0; i < bytyt; i++)
      {
        data += comdata[i];
      }
      p = "cmd=2&uid=" + UID +
          "&topic=" + TOPIC +
          "/up&msg=数据出错" + data + "\r\n"; // 推送消息
      client_bemfa_WiFiClient.print(p);
    }
  }
}

/*
 *在文件中查找指定数据并读取数据
 */
bool processFile(String time, char parameterData[])
{
  File file = SPIFFS.open("/charts.txt", "r");
  if (!file)
  {
    Serial.println("Failed to open file");
    return false;
  }
  // 读取月份数据
  size_t i;
  String content;
  // 日期，取值1~31
  int mday = atoi(time.c_str());
  int tm_mday = timeinfo.tm_mday;
  int tm_mon = timeinfo.tm_mon + 1;
  while (file.available())
  {
    content = file.readStringUntil('\n');
    if (mday != tm_mon)
    {
      tm_mday = getLastMonthDays(mday);
    }
    if (content.startsWith(time))
    {
      for (i = 0; i < tm_mday * 3 + 3; i++)
      {
        parameterData[i] = content[i];
      }
      parameterData[i++] = '\0'; // 确保字符串结束
      file.close();
      return true;
    }
    // Serial.println(time);
    // Serial.println(content);
    // Serial.println(parameterData);
  }
  file.seek(0);
  Serial.println(file.readString());
  file.close();
  return false;
}

/*
 *在文件中查找指定数据并修改后面的数据 W修改 , R读取
 */
void operateOnDataInFile(const char *filename, String targetChar, String action, float &newData)
{ // 使用 int 引用
  File file = SPIFFS.open(filename, "r+");
  targetChar += " :";
  if (!file)
  {
    Serial.println("Failed to open file");
    File file = SPIFFS.open(filename, "w"); // 如果文件不存在，创建新文件
  }
  if (newData > 1000000000) // 如果写入的数据超出存储空间
  {
    Serial.println("超出数据范围");
    return;
  }

  bool found = false; // 用于标记是否找到匹配行

  String content;
  while (file.available())
  {
    content = file.readStringUntil('\n');
    if (content.startsWith(targetChar))
    { // 使用 startsWith 更简洁地判断是否匹配
      found = true;
      if (action == "R")
      {
        Serial.println("读取数据");
        String data = content.substring(targetChar.length()); // 提取目标字符后面的数据
        newData = data.toFloat();                             // 将字符串转换为 int 并传出
                                                              // Serial.print(newData);
                                                              // Serial.print("\n");
      }
      else if (action == "W")
      {
        Serial.println("更新数据");
        file.seek(file.position() - (content.length() - targetChar.length()));
        while (file.peek() != '\n')
        {
          file.write(" ");
        }
        file.seek(file.position() - (content.length() - targetChar.length() - 1));
        file.print(newData);
      }
      break; // 找到匹配后就不再继续查找
    }
  }

  if (!found)
  { // 如果未找到匹配且是写入操作
    Serial.println("新建数据");
    file.print(targetChar);
    for (size_t i = 0; i < 11; i++)
    {
      file.write(" ");
    }
    file.print("\n");
    file.seek(file.position() - 11);
    file.print(newData);
  }
  // file.seek(0);
  // Serial.println(file.readString());
  file.close();
}

/*
 *计算今天还要多少秒并返回值
 */
long calculateTimeUntilMidnight(struct tm timeNow)
{
  long hoursUntilMidnight = 3600 * timeNow.tm_hour;
  long minutesUntilMidnight = 60 * timeNow.tm_min;
  long secondsUntilMidnight = timeNow.tm_sec;
  long totalSecondsUntilMidnight = 86400 - hoursUntilMidnight - minutesUntilMidnight - secondsUntilMidnight;
  // totalSecondsUntilMidnight = 60 - secondsUntilMidnight;
  Serial.printf("当前时间");
  Serial.printf(asctime(&timeNow));
  Serial.printf("还有%ld秒后运行回调函数", totalSecondsUntilMidnight);
  Serial.println("");
  return totalSecondsUntilMidnight; // 返回秒数
}

int getLastMonthDays(int currentMonth)
{
  if (currentMonth == 1)
  {
    return 31;
  }
  else if (currentMonth == 2)
  {
    int year = timeinfo.tm_year + 1900;
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
      return 29;
    }
    else
    {
      return 28;
    }
  }
  else if (currentMonth == 4 || currentMonth == 6 || currentMonth == 9 || currentMonth == 11)
  {
    return 30;
  }
  else
  {
    return 31;
  }
}

/*
 *写入图表数据函数
 */
void genxincharts()
{
  if (genxin)
  {
    if (getLocalTime(&timeinfo))
    {
      // struct tm {
      // int tm_sec; // 秒，取值0~59；
      // int tm_min; // 分，取值0~59；
      // int tm_hour; // 时，取值0~23；
      // int tm_mday; // 月中的日期，取值1~31；
      // int tm_mon; // 月，取值0~11；
      // int tm_year; // 年，其值等于实际年份减去1900；
      // int tm_wday; // 星期，取值0~6，0为周日，1为周一，依此类推；
      // int tm_yday; // 年中的日期，取值0~365，0代表1月1日，1代表1月2日，依此类推；
      // int tm_isdst; // 夏令时标识符，实行夏令时的时候，tm_isdst为正；不实行夏令时的进候，tm_isdst为0；不了解情况时，tm_isdst()为负
      // };
      // 打开文件
      File charts = SPIFFS.open("/charts.txt", "r+"); // 储存每天用电数文件
      if (!charts)
      {
        Serial.println("Failed to open charts");
        charts = SPIFFS.open("/charts.txt", "w+"); // 如果文件不存在，创建新文件
      }
      // 月份，取值0~11
      int tm_mon = timeinfo.tm_mon + 1;
      // 日期，取值1~31
      int tm_mday = timeinfo.tm_mday;
      // 计算今天用的电量
      float jintianKWh = KWh - ZuoTiaKWh;
      // 记录行数据
      String content = "";
      // 日初状态标志
      bool richu = !timerTick.active();
      // 月初状态标志
      bool yunchu = (!dsq && tm_mday == 1);

      if (!charts.available())
      { // 当没有记录数据初始化
        for (size_t month = 1; month <= 12; month++)
        {

          charts.printf("%02d", month);
          charts.printf("-");
          int mday = getLastMonthDays(month);
          Serial.println(mday);
          for (size_t day = 1; day <= mday; day++)
          {
            charts.printf("000");
          }
          charts.println();
        }
        charts.seek(0);
      }

      if (richu)
      {
        tm_mday--;
        if (yunchu)
        {
          if (tm_mon == 1)
          {
            tm_mon = 12;
          }
          else
          {
            tm_mon = tm_mon - 1;
          }
          tm_mday = getLastMonthDays(tm_mon);
        }
      }

      // Serial.println("文件数据");
      // while (charts.available())
      // {
      //   content = charts.readStringUntil('\n');
      //   Serial.println(content);
      // }
      // charts.seek(0);

      while (charts.available())
      {
        // 记录当月份行起始位置
        int seek = charts.position();
        content = charts.readStringUntil('\n');
        // 查找记录当月份行
        // 转换月份数据
        char mon[5];
        sprintf(mon, "%02d", tm_mon);
        if (content.startsWith(mon))
        {
          seek = seek + 3 + ((tm_mday - 1) * 3);
          charts.seek(seek);
          if (jintianKWh < 10)
          {
            charts.printf("%03d", int(jintianKWh * 100));
          }
          else
          {
            charts.printf("%03d", int(jintianKWh * 10));
          }
          Serial.println("seek=" + String(seek));
          Serial.println("记录" + String(jintianKWh));
          // 关闭文件
          charts.close();
          break;
        }
      }
      if (richu)
      { // 更新昨天电量
        doHttpStick("今天用电: " + String(jintianKWh) + " 度");
        ZuoTiaKWh = KWh;
        operateOnDataInFile("/data.txt", "ZuoTiaKWh", "W", ZuoTiaKWh);
        // 月初重新记录
        if (yunchu)
        {
          doHttpStick("上月用电: " + String(KWh) + " 度");
          Ot = 0;
          ZuoTiaKWh = 0;
          operateOnDataInFile("/data.txt", "Ot", "W", Ot);
          operateOnDataInFile("/data.txt", "ZuoTiaKWh", "W", ZuoTiaKWh);
        }
      }
    }
    genxin = false;
  }
}

/*
 *定时中断函数
 */
void yourFunction()
{
  genxin = true;
}

void diangshiqi()
{
  struct tm timeinfo;
  if (!timerTick.active() && getLocalTime(&timeinfo))
  {
    Serial.println("定时成功");
    long timeUntilMidnight1 = calculateTimeUntilMidnight(timeinfo);
    timerTick.once(timeUntilMidnight1, yourFunction); // 重新设置下一天的触发
  }
}
//  微信消息推送函数
void doHttpStick(String msg)
{
  String type = "2"; // 1表示是预警消息，2表示设备提醒消息
  WiFiClient WiFi;
  HTTPClient http;
  String ApiUrl = "http://api.bemfa.com/api/wechat/v1/"; // 默认 api 网址
  String postData = "uid=" + UID + "&type=" + type + "&device=" + Name + "&msg=" + msg;
  http.begin(WiFi, ApiUrl);                                            // Specify request destination
  http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Specify content-type header
  http.POST(postData);                                                 // Send the request
  http.end();                                                          // Close connection
}
// 初始化，相当于main 函数
void setup()
{
  Serial.begin(4800);
  pinMode(buttonPin, INPUT_PULLUP);                                       // 设置led引脚为输入引脚
  attachInterrupt(digitalPinToInterrupt(buttonPin), checkSwitch, RISING); // 设置中断
  pinMode(OUT_Pin, OUTPUT);
  pinMode(LedBlink, OUTPUT);
  Serial.println("");
  Serial.println("Beginning...");
  loadConfig(); // 读取存储数据
  waitKey();
  doWiFiTick();                                      // 连接网络
  UID = config.cuid;                                 // 赋值UID
  TOPIC = config.ctopic;                             // 赋值TOPIC
  detachInterrupt(digitalPinToInterrupt(buttonPin)); // 删除外部中断
  ButtonConfig *buttonConfig = ButtonConfig::getSystemButtonConfig();
  buttonConfig->setEventHandler(handleEvent);
  buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  buttonConfig->setFeature(ButtonConfig::kFeatureRepeatPress);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterLongPress);
  buttonConfig->setLongPressDelay(5000);                    // 长按时间5秒
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // 时间初始化
}
// 循环
void loop()
{
  doWiFiTick();      // 检查wifi
  doTCPClientTick(); // tcp消息接收
  ledButton.check(); // 按键检查
  Serialjieshou();   // AC
  genxincharts();    // 定时中断函数运行
}
